package cc.autoapi.pucong.auto.flow.spring.boot.start.extTask;


import cc.autoapi.pucong.auto.flow.core.flow.execNode.ai.send.AbstractAiSendSendTool;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.entity.api.ApiHandlerMsgDto;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.entity.log.LogNodeHandlerMsgDto;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.entity.node.*;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.exec.DataBus;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.exec.publish.ProcessExecApiPublish;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.exec.publish.ProcessExecLogPublish;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.selfenum.DataTypeEnum;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.selfenum.LogLevelTypeEnum;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.selfenum.NodeTypeEnum;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.task.AbstractTask;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.task.TaskFactory;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.task.extend.AbstractExtendTask;
import cc.autoapi.pucong.auto.flow.core.flow.execNode.utils.DataValueUtils;
import cc.autoapi.pucong.auto.flow.core.root.utils.ApplicationContextUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import org.apache.commons.lang3.exception.ExceptionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class AiToolsTask extends AbstractExtendTask {

    private List<NodeParamIn> paramsIns;
    private List<NodeParamOut> paramsOuts;
    private JSONArray messages = new JSONArray();

    public AiToolsTask() {
        paramsIns = new ArrayList<>();
        NodeParamIn system = new NodeParamIn()
                .setParamCode("system")
                .setDateType(DataTypeEnum.TXT)
                .setDesc("系统提示词")
                .setNeedValue(false);
        paramsIns.add(system);
        NodeParamIn question = new NodeParamIn()
                .setParamCode("question")
                .setDateType(DataTypeEnum.TXT)
                .setDesc("问题内容")
                .setNeedValue(true);
        paramsIns.add(question);

        paramsOuts = new ArrayList<>();
        NodeParamOut msg = new NodeParamOut()
                .setParamCode("msg")
                .setDesc("ai回答结果")
                .setDateType(DataTypeEnum.STRING);
        paramsOuts.add(msg);
    }

    @Override
    public void run() {
        ExecParamIn system = getParamInInfo("system");
        Object systemObj = system.getDateValue();
        String systemStr = systemObj.toString();

        ExecParamIn question = getParamInInfo("question");
        Object questionObj = question.getDateValue();
        String questionStr = questionObj.toString();

        AbstractAiSendSendTool sendSendTool = (AbstractAiSendSendTool) ApplicationContextUtil.getBean(AbstractAiSendSendTool.class);
        // 查询工具信息
        JSONArray tools = getToolsInfo();
        sendSendTool.initTools(tools);
        JSONObject systemMsg = new JSONObject();
        systemMsg.put("role", "system");
        systemMsg.put("content", systemStr);
        JSONObject startNodeInfo = new JSONObject();
        startNodeInfo.put("role", "user");
        startNodeInfo.put("content", questionStr);
        messages.add(systemMsg);
        messages.add(startNodeInfo);
        JSONObject jsonObject = sendSendTool.sendToolsToAi(messages);
        // 使用工具
        String result = userTool(sendSendTool, jsonObject);
        setParamOutValue("msg", result);
    }

    private String userTool(AbstractAiSendSendTool sendSendTool, JSONObject jsonObject) {
        messages.add(jsonObject);
        JSONArray toolCalls = jsonObject.getJSONArray("tool_calls");
        if (toolCalls != null) {
            // 异步执行
            for (Object toolCall : toolCalls) {
                JSONObject toolCallJson = (JSONObject) toolCall;
                String id = toolCallJson.getString("id");
                String toolCallName = toolCallJson.getJSONObject("function").getString("name");
                JSONObject toolCallArguments = toolCallJson.getJSONObject("function").getJSONObject("arguments");
                // 执行工具
                JSONObject toolsResult = runResult(toolCallName, toolCallArguments);
                // 设置结果
                JSONObject msg = new JSONObject();
                msg.put("role", "tool");
                msg.put("tool_call_id", id);
                msg.put("name", toolCallName);
                msg.put("content", JSON.toJSONString(toolsResult));
                messages.add(msg);
            }
            // 再次调用
            JSONObject jsonObject1 = sendSendTool.sendToolsToAi(messages);
            return userTool(sendSendTool, jsonObject1);
        } else {
            return jsonObject.getString("content");
        }
    }

    private JSONObject runResult(String toolCallName, JSONObject toolCallArguments) {
        ProcessExecApiPublish apiPublish = DataBus.getApiPublish();
        ProcessExecLogPublish nodeLogPublish = DataBus.getNodeLogPublish();
        String instanceId = getInstanceId();
        JSONObject result = new JSONObject();
        //执行工具组件
        ExecNode execNode = getExecNode();
        String nodeId = execNode.getUuid();
        String apiId = getApiId();
        // 查询下一节点
        List<TaskNodeRelation> taskNodeRelationList = DataBus.getTaskNodeRelationList(apiId);
        List<String> nextNodeIdList = taskNodeRelationList.stream().filter(relation -> {
            return relation.getNodeFromId().equals(nodeId) && relation.getRelationType() == 2;
        }).map(relation -> relation.getNodeToId()).collect(Collectors.toList());
        List<TaskNode> execNodeList = DataBus.getTaskNodeList(apiId);
        Map<String, TaskNode> execParamInHashMap = execNodeList.stream().collect(Collectors.toMap(TaskNode::getUuid, s -> s));
        for (String id : nextNodeIdList) {
            TaskNode taskNode = execParamInHashMap.get(id);
            if (taskNode != null) {
                String nodeName = taskNode.getNodeName();
                if (toolCallName.equals(getPinYin(nodeName))) {
                    ExecNode execNode1 = new ExecNode(taskNode);
                    if (NodeTypeEnum.FOR.getKey().equals(taskNode.getNodeType())) {
                        execNode1 = new ExecNodeFor(taskNode);
                    } else if (NodeTypeEnum.PROCESS.getKey().equals(taskNode.getNodeType())) {
                        execNode1 = new ExecNodeProcess(taskNode);
                    }
                    DataBus.addExecNodeList(instanceId, execNode1);
                    AbstractTask task = TaskFactory.getTask(execNode1.getNodeType());
                    task.setApiId(apiId);
                    task.setInstanceId(instanceId);
                    task.setExecNode(execNode1);
                    // 设置输入参数
                    List<ExecParamIn> paramInList = execNode1.getParamInList();
                    for (ExecParamIn execParamIn : paramInList) {
                        String paramCode = execParamIn.getParamCode();
                        DataTypeEnum dateType = execParamIn.getDateType();
                        // 获取参数值
                        Object dateValue = toolCallArguments.get(paramCode);
                        // 转换数据类型
                        execParamIn.setDateValue(DataValueUtils.transDataType(paramCode, dateType, dateValue));
                    }
                    nodeLogPublish.publicMessageByAsync(LogNodeHandlerMsgDto.start(apiId, instanceId, execNode1.getUuid(), execNode1.getInstanceNodeId()));
                    printSelfInLog(execNode1.getUuid(), execNode1.getInstanceNodeId(), paramInList);
                    // 执行
                    try {
                        task.run();
                        // 成功通知
                        nodeLogPublish.publicMessageByAsync(LogNodeHandlerMsgDto.ok(apiId, instanceId, execNode1.getUuid(), execNode1.getInstanceNodeId(), false));
                    } catch (Exception e) {
                        String stackTrace = ExceptionUtils.getStackTrace(e);
                        String logContent = String.format("节点执行失败,api实例id：%s，节点名称:%s,\r error: %s", instanceId, execNode.getNodeName(), stackTrace);
                        log.error(logContent, e);
                        apiPublish.publicMessageByAsync(ApiHandlerMsgDto.error(apiId, instanceId, logContent));
                        apiPublish.publicMessageByAsync(ApiHandlerMsgDto.log(apiId, instanceId, LogLevelTypeEnum.ERROR, logContent));
                        nodeLogPublish.publicMessageByAsync(LogNodeHandlerMsgDto.error(apiId, instanceId, execNode1.getUuid(), execNode1.getInstanceNodeId()));
                        nodeLogPublish.publicMessageByAsync(LogNodeHandlerMsgDto.log(apiId, instanceId, execNode1.getUuid(), execNode1.getInstanceNodeId(), LogLevelTypeEnum.ERROR, logContent));
                        execNode.setNodeExecResultError(logContent);
                    }
                    // 获取结果
                    List<ExecParamOut> paramOutList = execNode1.getParamOutList();
                    for (ExecParamOut execParamOut : paramOutList) {
                        Object dataValue = execParamOut.getDateValue();
                        String paramCode = execParamOut.getParamCode();
                        result.put(paramCode, dataValue);
                    }
                    printSelfOutLog(execNode1.getUuid(), execNode1.getInstanceNodeId(), paramOutList);
                }
            }
        }
        return result;
    }

    private void printSelfInLog(String nodeId, String instanceNodeId, List<ExecParamIn> execNodeParamInList) {
        JSONArray mess = new JSONArray();
        for (ExecParamIn execParamIn : execNodeParamInList) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("paramCode", execParamIn.getParamCode());
            jsonObject.put("参数值", execParamIn.getParamValue());
            jsonObject.put("值", execParamIn.getDateValue());
            jsonObject.put("是否必填", execParamIn.getNeedValue() ? "必填" : "选填");
            // 参数类型 0静态值 1前置接口返回值 2全局参数
            jsonObject.put("参数类型", getParamDataType(execParamIn.getParamDataType()));
            jsonObject.put("参数位置", execParamIn.getParamPosition() == null ? null : execParamIn.getParamPosition().name());
            jsonObject.put("数据类型", execParamIn.getDateType().getName());
            mess.add(jsonObject);
        }
        String apiId = getApiId();
        String instanceId = getInstanceId();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("输入参数", mess);
        String jsonString = jsonObject.toJSONString(JSONWriter.Feature.PrettyFormat);
        ProcessExecLogPublish nodeLogPublish = DataBus.getNodeLogPublish();
        nodeLogPublish.publicMessageByAsync(LogNodeHandlerMsgDto.log(apiId, instanceId, nodeId, instanceNodeId, LogLevelTypeEnum.INFO, jsonString));
    }

    /**
     * 参数类型 0静态值 1前置接口返回值 2全局参数
     *
     * @param paramDataType
     * @return
     */
    private String getParamDataType(Integer paramDataType) {
        if (paramDataType == 0) {
            return "静态值";
        } else if (paramDataType == 1) {
            return "前置接口返回值";
        } else if (paramDataType == 2) {
            return "全局参数";
        }
        return null;
    }

    private void printSelfOutLog(String nodeId, String instanceNodeId, List<ExecParamOut> paramOutList) {
        JSONArray mess = new JSONArray();
        for (ExecParamOut execParamOut : paramOutList) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("paramCode", execParamOut.getParamCode());
            jsonObject.put("值", execParamOut.getDateValue());
            jsonObject.put("是否是流程返回值", execParamOut.getReturnValue());
            jsonObject.put("是否循环结束", execParamOut.getIsBreakParams());
            jsonObject.put("数据类型", execParamOut.getDateType().getName());
            mess.add(jsonObject);
        }
        String apiId = getApiId();
        String instanceId = getInstanceId();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("输出参数", mess);
        String jsonString = jsonObject.toJSONString(JSONWriter.Feature.PrettyFormat);
        ProcessExecLogPublish nodeLogPublish = DataBus.getNodeLogPublish();
        nodeLogPublish.publicMessageByAsync(LogNodeHandlerMsgDto.log(apiId, instanceId, nodeId, instanceNodeId, LogLevelTypeEnum.INFO, jsonString));
    }

    private JSONArray getToolsInfo() {
        JSONArray tools = new JSONArray();
        ExecNode execNode = getExecNode();
        String nodeId = execNode.getUuid();
        String apiId = getApiId();
        // 查询下一节点
        List<TaskNodeRelation> taskNodeRelationList = DataBus.getTaskNodeRelationList(apiId);
        List<String> nextNodeIdList = taskNodeRelationList.stream().filter(relation -> {
            return relation.getNodeFromId().equals(nodeId) && relation.getRelationType() == 2;
        }).map(relation -> relation.getNodeToId()).collect(Collectors.toList());
        List<TaskNode> execNodeList = DataBus.getTaskNodeList(apiId);
        Map<String, TaskNode> execParamInHashMap = execNodeList.stream().collect(Collectors.toMap(TaskNode::getUuid, s -> s));
        for (String id : nextNodeIdList) {
            TaskNode execNode1 = execParamInHashMap.get(id);
            if (execNode1 != null) {
                JSONObject tool = new JSONObject();
                tool.put("type", "function");
                JSONObject function = new JSONObject();
                function.put("name", getPinYin(execNode1.getNodeName()));
                function.put("description", execNode1.getNodeDesc());
                JSONObject parameters = new JSONObject();
                // 输入参数
                parameters.put("type", "object");
                JSONObject properties = new JSONObject();
                JSONArray required = new JSONArray();
                List<TaskNodeParamIn> taskNodeParamInList = execNode1.getTaskNodeParamInList();
                for (TaskNodeParamIn taskNodeParamIn : taskNodeParamInList) {
                    String paramCode = taskNodeParamIn.getParamCode();
                    DataTypeEnum dateType = taskNodeParamIn.getDateType();
                    String paramDesc = taskNodeParamIn.getParamDesc();
                    Boolean needValue = taskNodeParamIn.getNeedValue();
                    if (needValue) {
                        required.add(paramCode);
                    }
                    JSONObject propertyValue = new JSONObject();
                    propertyValue.put("type", getToolsDataType(dateType));
                    propertyValue.put("description", paramDesc);
                    properties.put(paramCode, propertyValue);
                }
                parameters.put("required", required);
                parameters.put("properties", properties);
                function.put("parameters", parameters);
                tool.put("function", function);
                tools.add(tool);
            }
        }
        return tools;
    }

    private String getPinYin(String nodeName) {
        StringBuilder pinyinSbf = new StringBuilder();
        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        format.setVCharType(HanyuPinyinVCharType.WITH_V);

        char[] input = nodeName.trim().toCharArray();
        try {
            for (char c : input) {
                // 如果是汉字，则转换为拼音
                if (Character.toString(c).matches("[\\u4E00-\\u9FA5]+")) {
                    String[] temp = PinyinHelper.toHanyuPinyinStringArray(c, format);
                    pinyinSbf.append(temp[0]);
                } else {
                    // 非汉字字符直接添加
                    pinyinSbf.append(c);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        String pinyin = pinyinSbf.toString().trim();
        int end = pinyin.length() > 100 ? 100 : pinyin.length();
        pinyin = pinyin.substring(0, end);
        return pinyin;
    }

    private String getToolsDataType(DataTypeEnum dateType) {
        switch (dateType) {
            case NUMBER:
                return "integer";
            case STRING:
            case TXT:
                return "string";
            case BOOLEAN:
                return "boolean";
            case MAP:
                return "object";
            case LIST:
                return "array";
            default:
                return "string";
        }
    }

    @Override
    public List<TaskNode> fitterNextPublishNode(List<TaskNode> nextNodeList) {
        List<TaskNodeRelation> taskNodeRelationList = DataBus.getTaskNodeRelationList(getApiId());
        List<TaskNodeRelation> collect = taskNodeRelationList.stream().filter(item -> {
            return item.getRelationType() == 0 && item.getNodeFromId().equals(getExecNode().getUuid());
        }).collect(Collectors.toList());
        List<TaskNode> nextNodes = new ArrayList<>();
        if (collect.size() > 0) {
            for (TaskNodeRelation relation : collect) {
                String nextNodeId = relation.getNodeToId();
                for (TaskNode taskNode : nextNodeList) {
                    if (nextNodeId.equals(taskNode.getUuid())) {
                        nextNodes.add(taskNode);
                    }
                }
            }
        }
        return nextNodes;
    }

    @Override
    public String getTaskName() {
        return "ai对话+工具";
    }

    @Override
    public String getTaskDesc() {
        return "本组件是可以进行工具调用的ai对话组件，下一流程的关系类型为2时，则会把下一组件当做工具进行使用。";
    }

    @Override
    public String getTaskSvgIcon() {
        return "<svg t=\"1731333596167\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"5163\" width=\"32\" height=\"32\"><path d=\"M116.946489 353.848889a116.92563 116.92563 0 0 1 32.009481 229.451852c3.034074 4.664889 5.006222 10.088296 5.688889 15.928889l0.227556 4.399407v115.067259a37.925926 37.925926 0 0 0 14.336 29.696l4.096 2.806519 319.184592 191.525926a37.925926 37.925926 0 0 0 34.398815 2.389333l4.626963-2.389333 356.048593-213.598815a37.925926 37.925926 0 0 1 42.704592 62.577778l-3.678814 2.465185-356.048593 213.598815a113.777778 113.777778 0 0 1-110.212741 3.792592l-6.826666-3.792592-319.222519-191.525926a113.777778 113.777778 0 0 1-54.992593-90.074074l-0.265481-7.471408v-115.067259c0-7.509333 2.199704-14.487704 5.95437-20.366222a116.92563 116.92563 0 0 1 31.971556-229.451852z m375.732148-20.745482a75.851852 75.851852 0 0 1 16.004741 14.52563l4.323555 5.802667 44.525037 65.877333a75.851852 75.851852 0 0 1 12.743111 36.02963l0.265482 6.447407v235.70963a37.925926 37.925926 0 0 1-75.58637 4.437333l-0.265482-4.437333v-85.940148l-3.26163 0.189629h-82.450962l-3.337482-0.189629v85.940148a37.925926 37.925926 0 0 1-75.58637 4.437333l-0.227556-4.437333v-235.70963a75.851852 75.851852 0 0 1 9.595259-36.977778l3.413334-5.499259 44.525037-65.877333a75.851852 75.851852 0 0 1 105.320296-20.328297z m163.612444 161.564445a37.925926 37.925926 0 0 1 37.698371 33.526518l0.227555 4.399408v164.901926a37.925926 37.925926 0 0 1-75.58637 4.437333l-0.265481-4.437333v-164.901926a37.925926 37.925926 0 0 1 37.925925-37.925926zM563.713896 25.031111l6.826667 3.792593 319.222518 191.525926a113.777778 113.777778 0 0 1 54.992593 90.112l0.265482 7.433481v115.105185c0 3.185778-0.379259 6.257778-1.137778 9.216a116.92563 116.92563 0 1 1-73.576297 0 32.95763 32.95763 0 0 1-0.910222-4.816592l-0.227555-4.399408V317.895111a37.925926 37.925926 0 0 0-14.336-29.696l-4.096-2.806518-319.222519-191.525926a37.925926 37.925926 0 0 0-34.360889-2.351408l-4.626963 2.351408-356.048592 213.598814a37.925926 37.925926 0 0 1-42.704593-62.539851l3.678815-2.465186L453.501156 28.823704a113.777778 113.777778 0 0 1 110.21274-3.792593z m343.381334 487.120593a41.073778 41.073778 0 1 0 0 82.147555 41.073778 41.073778 0 0 0 0-82.147555zM450.2016 395.908741l-44.525037 65.877333-0.037926 74.258963 3.337482-0.151704h82.450962l3.26163 0.151704v-74.258963l-44.487111-65.877333z m-333.255111 33.754074a41.073778 41.073778 0 1 0 0 82.185481 41.073778 41.073778 0 0 0 0-82.147555z m539.344592-99.858963a37.925926 37.925926 0 0 1 37.698371 33.488592l0.227555 4.437334v41.225481a37.925926 37.925926 0 0 1-75.58637 4.399408l-0.265481-4.399408V367.729778a37.925926 37.925926 0 0 1 37.925925-37.925926z\" fill=\"#5a6ad8\" p-id=\"5164\"></path></svg>";
    }

    @Override
    public List<NodeParamIn> getPropertyParamsIns() {
        return paramsIns;
    }

    @Override
    public List<NodeParamOut> getPropertyParamsOuts() {
        return paramsOuts;
    }
}
